WorkManager基本使用及源码分析(三) - SystemAlarmService

2021年02月06日 99 字 Jetpack


目录


源码篇

上一篇中我们了解了WorkManager使用的的主要组件,猜测了各个组件的作用,并简单介绍了WorkManager是如何初始化的。本篇将延续前文,介绍WorkManager中Service组件之一的SystemAlarmService。

SystemAlarmService

Service invoked by {@link android.app.AlarmManager} to run work tasks.

服务将被AlarmManager调用来执行工作任务.

注: 此节中凡 描述中的 “命令”“intent” 均指代包括 [规划任务、重新规划任务、约束条件改变、执行完毕、延时执行、停止执行…] 的事件,可视作状态模式。

如何处置命令 (processCommand)

SystemAlarmService 主要逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SystemAlarmService extends LifecycleService
implements SystemAlarmDispatcher.CommandsCompletedListener {
// do something
// onCreate时执行initializeDispatcher()初始化并绑定闹钟事件调度器
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
// do something
// 添加调度事件,若当前无任务则立即调度
if (intent != null) {
mDispatcher.add(intent, startId);
}

// 如果服务崩溃,我们希望所有未确认的意图都得到重新交付。
return Service.START_REDELIVER_INTENT;
}
}

我们看到, 此服务在创建时初始化并绑定闹钟事件调度器,当服务被start时,若有intent事件,则将intent传递给调度器. 调度器收到intent后执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@MainThread
public boolean add(@NonNull final Intent intent, final int startId) {
assertMainThread();
// do something
// 保证命令列表安全,只进或只出
synchronized (mIntents) {
boolean hasCommands = !mIntents.isEmpty();
mIntents.add(intent);
if (!hasCommands) {
// 如果是第一个命令则执行
// 一般情况下,若上一个命令执行完毕会被移除
processCommand();
}
}
return true;
}

当闹钟调度器收到新命令,会将新命令放入命令集合,若新命令为集合中第一个命令则直接进入执行命令逻辑中,否则不处理,前一命令执行完毕后再次调用执行命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@MainThread
private void processCommand() {
// do something
mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable() {
// do something
try {
// 若当前命令不为空,则将命令交给状态机执行
mCommandHandler.onHandleIntent(mCurrentIntent, startId, SystemAlarmDispatcher.this);
} catch (Throwable throwable) {}
finally {
// 当前命令执行完毕,回调SystemAlarmDispatcher处理命令集以及后续操作
postOnMainThread( new DequeueAndCheckForCompletion(SystemAlarmDispatcher.this));
}
}
}

processCommand被调用,将开启新的线程处理命令,主要干了两件事:

  • 将命令交给CommandHandler分发[规划任务、重新规划任务、约束条件改变、执行完毕…]事件
  • 将事件处理完毕回调到闹钟事件调度器,由调度器处理后续事宜。

我们来依次跟进这两件事:

CommandHandler如何分发命令

我们前面看到processCommand中调用CommandHandler分发命令,其代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@WorkerThread
void onHandleIntent(@NonNull Intent intent,int startId, @NonNull SystemAlarmDispatcher dispatcher) {

String action = intent.getAction();

if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
// 约束条件改变
handleConstraintsChanged(intent, startId, dispatcher);
} else if (ACTION_RESCHEDULE.equals(action)) {
// 异常中断,需重新规划
handleReschedule(intent, startId, dispatcher);
} else {
Bundle extras = intent.getExtras();
if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
// log error
} else {
if (ACTION_SCHEDULE_WORK.equals(action)) {
// 规划任务
handleScheduleWorkIntent(intent, startId, dispatcher);
} else if (ACTION_DELAY_MET.equals(action)) {
// 时延到点
handleDelayMet(intent, startId, dispatcher);
} else if (ACTION_STOP_WORK.equals(action)) {
// 取消任务
handleStopWork(intent, dispatcher);
} else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
// 完成任务
handleExecutionCompleted(intent, startId);
} else {
// log error
}
}
}
}

我们看到这里主要依据命令(intent)的action执行命令分发操作,此处我们主要关注handleDelayMet 时延结束后,处理任务的主线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void handleDelayMet(
@NonNull Intent intent,
int startId,
@NonNull SystemAlarmDispatcher dispatcher) {

Bundle extras = intent.getExtras();
// 保证任务处理线程安全
synchronized (mLock) {
String workSpecId = extras.getString(KEY_WORKSPEC_ID);

// Check to see if we are already handling an ACTION_DELAY_MET for the WorkSpec.
// If we are, then there is nothing for us to do.
// 检查该任务是否已经在即将执行队伍(map)中,如果是则不再重复操作
if (!mPendingDelayMet.containsKey(workSpecId)) {
DelayMetCommandHandler delayMetCommandHandler = new DelayMetCommandHandler(mContext, startId, workSpecId, dispatcher);
// 将任务放入即将执行队伍(map)中
mPendingDelayMet.put(workSpecId, delayMetCommandHandler);
// 处理任务
delayMetCommandHandler.handleProcessWork();
} else {
// log error
}
}
}

我们可以看到,CommandHandler中维护了一个 mPendingDelayMet的Map来保证单次任务不被多次处理,若命令未被处理过,则将其加入到队伍中,然后调用delayMetCommandHandler.handleProcessWork()去处理任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WorkerThread
void handleProcessWork() {
// 创建WakeLocks
mWakeLock = WakeLocks.newWakeLock(...);
// 获取WorkSpec
WorkSpec workSpec = mDispatcher.getWorkManager().getWorkDatabase().workSpecDao().getWorkSpec(mWorkSpecId);
// 处理通常不会发生的情况 - 未获取到WorkSpec。
// 取消工作应该删除alarm,但是如果alarm已经触发,那么就触发一个stop work请求来删除挂起的delay met命令处理程序。
if (workSpec == null) {
stopWork();
return;
}
// 跟踪工作任务是否有约束
mHasConstraints = workSpec.hasConstraints();

if (!mHasConstraints) {
// 若无约束
onAllConstraintsMet(Collections.singletonList(mWorkSpecId));
} else {
// 允许跟踪器报告约束更改
// 此处可见,我们可以将时延视作一种特殊的约束
mWorkConstraintsTracker.replace(Collections.singletonList(workSpec));
}
}

这里判断了任务状态及约束状态,依据约束状态执行任务或更新任务状态,
执行任务时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
// 保证任务正确
if (!workSpecIds.contains(mWorkSpecId)) {
return;
}
// 保证执行安全
synchronized (mLock) {
if (mCurrentState == STATE_INITIAL) {
// 更改状态
mCurrentState = STATE_START_REQUESTED;
// startWork => doWork
//这里没有使用WorkManagerImpl#startWork(),因为我们需要知道处理器是否在这里将工作排队
boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
// 若任务状态是轮询
if (isEnqueued) {
// 设置计时器以强制已进入队列的任务配额
mDispatcher.getWorkTimer()
.startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
} else {
// 执行完毕或取消,则清除状态
cleanUp();
}
} else {
Logger.get().debug(TAG, String.format("Already started work for %s", mWorkSpecId));
}
}
}

这里最终改变任务状态并调用Processor.startWork()执行任务,我们将关注Processor.startWork(),这个方法最终执行到我们定义的Worker中doWork()方法,并且前面说到的WorkManager中负责任务执行的Service们最终都会调用到Processor.startWork()

由于内容流程较长且属于公共流程,关于Processor.startWork()将单独整理未一小节,点击此处查看

DequeueAndCheckForCompletion如何完成回调

DequeueAndCheckForCompletion 是一个Runnable对象,postOnMainThread时调用run:

1
2
3
4
@Override
public void run() {
mDispatcher.dequeueAndCheckForCompletion();
}

最终回调到dequeueAndCheckForCompletion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void dequeueAndCheckForCompletion() {
// 保证mIntents命令集只进或只出
synchronized (mIntents) {
if (mCurrentIntent != null) {
// 从命令集中移除已完成(第一个)命令
if (!mIntents.remove(0).equals(mCurrentIntent)) {
throw new IllegalStateException("something");
}
mCurrentIntent = null;
}
SerialExecutor serialExecutor = mTaskExecutor.getBackgroundExecutor();
// 若无预计执行和待执行命令,则通知SystemAlarmService执行完毕
if (!mCommandHandler.hasPendingCommands()
&& mIntents.isEmpty()
&& !serialExecutor.hasPendingTasks()) {
if (mCompletedListener != null) {
mCompletedListener.onAllCommandsCompleted();
}
} else if (!mIntents.isEmpty()) {
// 若命令集中还有未执行的命令,则继续执行
processCommand();
}
}
}

此时,若当前命令存在,则从命令集中移除当前命令。若无预计执行和待执行命令,则调用mCompletedListener.onAllCommandsCompleted()通知SystemAlarmService执行完毕;若命令集合中还有未执行命令,则调用processCommand()继续执行。

SystemAlarmService.onAllCommandsCompleted()最终会执行stopSelf()停止服务

假装下面是时序图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SystemAlarmService.java
|- initializeDispatcher(); // 初始化并绑定闹钟调度器
|- SystemAlarmService.onStartCommand() // 通过startService添加命令
|- SystemAlarmDispatcher.add() // 委托调度器添加命令
|- mIntents.add(intent); // 添加命令进入命令集
|- SystemAlarmDispatcher.processCommand() // 若是第一个则,执行命令, 若不是第一个,将在后续流程中继续执行
|- try CommandHandler.onHandleIntent()
|- ACTION_CONSTRAINTS_CHANGED
|- ...
|- ACTION_DELAY_MET
|- delayMetCommandHandler.handleProcessWork()
|- onAllConstraintsMet()
|- mDispatcher.getProcessor().startWork()
|- finally postOnMainThread( new DequeueAndCheckForCompletion(SystemAlarmDispatcher.this))// 分发完成命令事件
|- SystemAlarmDispatcher.dequeueAndCheckForCompletion() // 回调
|- mIntents.remove(0); // 从命令集移出(当前)第一个命令
|- CommandsCompletedListener.onAllCommandsCompleted()
OR SystemAlarmDispatcher.processCommand() // 命令集执行完毕 或 执行下一个命令